// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.

import AppKit

extension UILayoutOrientation {
  fileprivate var layoutTDK: CandidatePool.LayoutOrientation {
    switch self {
    case .horizontal:
      return .horizontal
    case .vertical:
      return .vertical
    @unknown default:
      return .horizontal
    }
  }
}

// MARK: - CtlCandidateTDK

public final class CtlCandidateTDK: CtlCandidate, NSWindowDelegate {
  // MARK: Lifecycle

  // MARK: - Constructors

  public required init(_ layout: UILayoutOrientation = .horizontal) {
    let contentRect = CGRect(x: 128.0, y: 128.0, width: 0.0, height: 0.0)
    let styleMask: NSWindow.StyleMask = [.nonactivatingPanel]
    let panel = NSPanel(
      contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: false
    )
    panel.level = NSWindow.Level(Int(max(CGShieldingWindowLevel(), kCGPopUpMenuWindowLevel)) + 2)
    panel.hasShadow = true
    panel.backgroundColor = NSColor.clear
    self.candidateView = Self.currentView

    super.init(layout)
    candidateView.controller = self
    window = panel
    Self.currentWindow = panel
    window?.delegate = self
    currentLayout = layout
    // 設置背景視覺效果視圖
    if !Self.shouldDisableVisualEffectView {
      if window?.contentView == nil {
        window?.contentView = NSView()
      }
      if let visualEffectView, let contentView = window?.contentView {
        contentView.addSubview(visualEffectView)
        visualEffectView.pinEdges(to: contentView)
      }
    }

    self.observation = Broadcaster.shared
      .observe(\.eventForClosingAllPanels, options: [.new]) { _, _ in
        self.visible = false
        Self.thePool.cleanData()
      }
  }

  @available(*, unavailable)
  required init?(coder _: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  deinit { observation?.invalidate() }

  // MARK: Public

  public static var currentMenu: NSMenu? {
    willSet {
      currentMenu?.cancelTracking()
    }
  }

  public static var currentWindow: NSWindow? {
    willSet {
      currentWindow?.orderOut(nil)
    }
  }

  // Already implemented in CandidatePool.
  override public var highlightedIndex: Int {
    get { Self.thePool.highlightedIndex }
    set {
      Self.thePool.highlight(at: newValue)
      updateDisplay()
    }
  }

  public var useCocoa: Bool = false
  public var useMouseScrolling: Bool = true

  /// FB10978412: Since macOS 11 Big Sur, CTFontCreateUIFontForLanguage cannot
  /// distinguish zh-Hans and zh-Hant with correct adoptation of proper PingFang SC/TC variants.
  /// Update: This has been fixed in macOS 13.
  ///
  /// Instructions for Apple Developer relations to reveal this bug:
  ///
  /// 0) Please go to Step 1. Reason: IMK Candidate Window support has been removed in this repo.
  /// 1) Make sure the usage of ".languageIdentifier" is disabled in the Dev Zone of the vChewing Preferences.
  /// 2) Run "make update" in the project folder to download the latest git-submodule of dictionary file.
  /// 3) Compile the target "vChewingInstaller", run it. It will install the input method into
  ///    "~/Library/Input Methods/" folder. Remember to ENABLE BOTH "vChewing-CHS"
  ///    and "vChewing-CHT" input sources in System Preferences / Settings.
  /// 4) Type Zhuyin "ej3" (ㄍㄨˇ) (or "gu3" in Pinyin if you enabled Pinyin typing in vChewing Preferences.)
  ///    using both "vChewing-CHS" and "vChewing-CHT", and check the candidate window by pressing SPACE key.
  /// 5) Do NOT enable either KangXi conversion mode nor JIS conversion mode. They are disabled by default.
  /// 6) Expecting the glyph differences of the candidate "骨" between PingFang SC and PingFang TC when rendering
  ///    the candidate window in different "vChewing-CHS" and "vChewing-CHT" input modes.
  override public func assignCandidateFont(name: String? = nil, size: Double) {
    let finalReturnFont: NSFont = {
      switch IMEApp.currentInputMode {
      case .imeModeCHS:
        return CTFontCreateUIFontForLanguage(.system, size, "zh-Hans" as CFString)
          ?? NSFont.systemFont(ofSize: size)
      case .imeModeCHT:
        return (
          prefs.shiftJISShinjitaiOutputEnabled || prefs
            .chineseConversionEnabled
        )
          ? CTFontCreateUIFontForLanguage(.system, size, "ja" as CFString)
          : CTFontCreateUIFontForLanguage(.system, size, "zh-Hant" as CFString)
      default:
        return CTFontCreateUIFontForLanguage(.system, size, nil)
      }
    }()
      ?? NSFont.systemFont(ofSize: size)
    if let name = name, !name.isEmpty {
      candidateFont = NSFont(name: name, size: size) ?? finalReturnFont
    } else {
      candidateFont = finalReturnFont
    }
  }

  override public func updateDisplay() {
    guard let window = window else { return }
    asyncOnMain { [weak self] in
      guard let this = self else { return }
      this.updateNSWindowModern(window)
    }
    useMouseScrolling = prefs.enableMouseScrollingForTDKCandidatesCocoa
    // 先擦除之前的反查结果。
    reverseLookupResult = []
    // 再更新新的反查结果。
    if let currentCandidate = Self.thePool.currentCandidate {
      let displayedText = currentCandidate.displayedText
      var lookupResult: [String?] = delegate?.reverseLookup(for: displayedText) ?? []
      if displayedText.count == 1, delegate?.showCodePointForCurrentCandidate ?? false {
        if lookupResult.isEmpty {
          lookupResult
            .append(currentCandidate.charDescriptions(shortened: !Self.thePool.isMatrix).first)
        } else {
          lookupResult.insert(
            currentCandidate.charDescriptions(shortened: true).first,
            at: lookupResult.startIndex
          )
        }
        reverseLookupResult = lookupResult.compactMap { $0 }
      } else {
        reverseLookupResult = lookupResult.compactMap { $0 }
        // 如果不提供 UNICODE 碼位資料顯示的話，則在非多行多列模式下僅顯示一筆反查資料。
        if !Self.thePool.isMatrix {
          reverseLookupResult = [reverseLookupResult.first].compactMap { $0 }
        }
      }
    }
    Self.thePool.reverseLookupResult = reverseLookupResult
    Self.thePool.tooltip = delegate?.candidateToolTip(shortened: !Self.thePool.isMatrix) ?? ""
    delegate?.candidatePairHighlightChanged(at: highlightedIndex)
  }

  override public func reloadData() {
    CandidateCellData.unifiedSize = candidateFont.pointSize
    guard let delegate = delegate else { return }
    Self.thePool.reinit(
      candidates: delegate.candidatePairs(conv: true), lines: maxLinesPerPage,
      isExpanded: delegate.shouldAutoExpandCandidates,
      selectionKeys: delegate.selectionKeys, layout: currentLayout.layoutTDK, locale: locale
    )
    Self.thePool.tooltip = tooltip
    Self.thePool.reverseLookupResult = reverseLookupResult
    Self.thePool.highlight(at: 0)
    updateDisplay()
  }

  override public func scrollWheel(with event: NSEvent) {
    guard useMouseScrolling else { return }
    handleMouseScroll(deltaX: event.deltaX, deltaY: event.deltaY)
  }

  // Already implemented in CandidatePool.
  @discardableResult
  override public func showNextPage() -> Bool {
    defer { updateDisplay() }
    return Self.thePool.flipPage(isBackward: false)
  }

  // Already implemented in CandidatePool.
  @discardableResult
  override public func showPreviousPage() -> Bool {
    defer { updateDisplay() }
    return Self.thePool.flipPage(isBackward: true)
  }

  // Already implemented in CandidatePool.
  @discardableResult
  override public func showPreviousLine() -> Bool {
    defer { updateDisplay() }
    return Self.thePool.consecutivelyFlipLines(isBackward: true, count: 1)
  }

  // Already implemented in CandidatePool.
  @discardableResult
  override public func showNextLine() -> Bool {
    defer { updateDisplay() }
    return Self.thePool.consecutivelyFlipLines(isBackward: false, count: 1)
  }

  // Already implemented in CandidatePool.
  @discardableResult
  override public func highlightNextCandidate() -> Bool {
    defer { updateDisplay() }
    return Self.thePool.highlightNeighborCandidate(isBackward: false)
  }

  // Already implemented in CandidatePool.
  @discardableResult
  override public func highlightPreviousCandidate() -> Bool {
    defer { updateDisplay() }
    return Self.thePool.highlightNeighborCandidate(isBackward: true)
  }

  // Already implemented in CandidatePool.
  override public func candidateIndexAtKeyLabelIndex(_ id: Int) -> Int? {
    Self.thePool.calculateCandidateIndex(subIndex: id)
  }

  // MARK: Private

  private static let shouldDisableVisualEffectView: Bool = {
    if #available(macOS 10.13, *) {
      return false
    }
    return true
  }()

  private static let thePool: CandidatePool = .init(candidates: [])
  private static let currentView: VwrCandidateTDKAppKit = .init(thePool: thePool)

  private let prefs = PrefMgr()

  @objc
  private var observation: NSKeyValueObservation?
  // 創建背景視覺效果視圖
  private let visualEffectView: NSView? = {
    if #available(macOS 27.0, *), NSApplication.uxLevel == .liquidGlass {
      #if compiler(>=6.2) && canImport(AppKit, _version: 26.0)
        let resultView = NSGlassEffectView()
        return resultView
      #endif
    }
    if #available(macOS 10.10, *), NSApplication.uxLevel != .none {
      let resultView = NSVisualEffectView()
      return resultView
    }
    return nil
  }()

  private let candidateView: VwrCandidateTDKAppKit

  private var candidateViewLegacy4Debug: NSView {
    let textField = NSTextField()
    textField.isSelectable = false
    textField.isEditable = false
    textField.isBordered = false
    textField.backgroundColor = .clear
    textField.allowsEditingTextAttributes = false
    textField.preferredMaxLayoutWidth = textField.frame.width
    textField.attributedStringValue = Self.thePool.attributedDescription
    textField.sizeToFit()
    textField.backgroundColor = .clear
    return textField
  }

  private func updateEffectView() {
    if #available(macOS 27.0, *), NSApplication.uxLevel == .liquidGlass {
      #if compiler(>=6.2) && canImport(AppKit, _version: 26.0)
        guard let resultView = visualEffectView as? NSGlassEffectView else { return }
        resultView.cornerRadius = Self.thePool.windowRadius
        resultView.style = .clear
        let bgTintColor: NSColor = !NSApplication.isDarkMode ? .white : .black
        resultView.wantsLayer = true
        resultView.layer?.cornerRadius = Self.thePool.windowRadius
        resultView.layer?.masksToBounds = true
        resultView.layer?.backgroundColor = bgTintColor.withAlphaComponent(0.1).cgColor
      #endif
    }
    if #available(macOS 10.10, *), NSApplication.uxLevel != .none {
      guard let resultView = visualEffectView as? NSVisualEffectView else { return }
      resultView.material = .titlebar
      resultView.blendingMode = .behindWindow
      resultView.state = .active
      // 設置圓角以保持原有的視覺特性
      resultView.wantsLayer = true
      resultView.layer?.cornerRadius = Self.thePool.windowRadius
      resultView.layer?.masksToBounds = true
    }
  }

  private func updateNSWindowModern(_ window: NSWindow) {
    // 同步 Metrics
    Self.thePool.updateMetrics()

    // fittingSize 跟随新的 metrics
    candidateView.invalidateIntrinsicContentSize()
    candidateView.setNeedsDisplay(candidateView.bounds)

    guard !Self.shouldDisableVisualEffectView else {
      window.isOpaque = false
      window.backgroundColor = .clear
      window.contentView = candidateView
      window.setContentSize(candidateView.fittingSize)
      delegate?.resetCandidateWindowOrigin()
      return
    }

    // 更新背景視覺效果視圖
    updateEffectView()

    let viewSize = candidateView.fittingSize
    guard let containerView = window.contentView else { return }
    containerView.setFrameSize(viewSize)
    // 為容器視圖也設置圓角，確保整體一致性
    containerView.wantsLayer = true
    containerView.layer?.cornerRadius = Self.thePool.windowRadius
    containerView.layer?.masksToBounds = true

    // 添加候選窗口內容視圖
    if containerView.subviews.allSatisfy({ !($0 is VwrCandidateTDKAppKit) }) {
      containerView.addSubview(candidateView)
      candidateView.pinEdges(to: containerView)
    }

    window.isOpaque = false
    window.backgroundColor = .clear
    window.contentView = containerView
    window.setContentSize(viewSize)
    delegate?.resetCandidateWindowOrigin()
  }

  private func handleMouseScroll(deltaX: CGFloat, deltaY: CGFloat) {
    switch (deltaX, deltaY, Self.thePool.layout) {
    case (0, 1..., .vertical), (1..., 0, .horizontal): highlightNextCandidate()
    case (..<0, 0, .horizontal), (0, ..<0, .vertical): highlightPreviousCandidate()
    case (0, 1..., .horizontal), (1..., 0, .vertical): showNextLine()
    case (0, ..<0, .horizontal), (..<0, 0, .vertical): showPreviousLine()
    case (_, _, _): break
    }
  }
}
